package com.example.redis.demo.service.impl;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.redis.demo.exception.BusinessException;
import com.example.redis.demo.mapper.SignUserLogMapper;
import com.example.redis.demo.model.SignUserLog;
import com.example.redis.demo.service.UserSignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;

@Service
public class UserSignServiceImpl extends ServiceImpl<SignUserLogMapper, SignUserLog> implements UserSignService {


    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private SignUserLogMapper signUserLogMapper;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doSign(Long userId, String userName, Date date) {
        //防止重复提交走缓存
        //获取当月的某天的偏移量
        int dayOfMonth = DateUtil.dayOfMonth(date);
        long offset = dayOfMonth - 1;
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMM");
        String formatDate = simpleDateFormat.format(date);
        Boolean hasSigned = this.redisTemplate.opsForValue().getBit("u:" + userId + ":sign:" + formatDate, offset);
        if (hasSigned != null && hasSigned) {
            throw BusinessException.errorOfCode("不能重复签到", "430");
        }
        //存储到数据库
        final SignUserLog signUserLog = new SignUserLog();
        signUserLog.setCreateTime(new Date());
        signUserLog.setCreateUser(userName);
        signUserLog.setIsDeleted(0);
        signUserLog.setSignDate(new Date());
        signUserLog.setUserId(userId);
        int res = this.signUserLogMapper.insert(signUserLog);
        if (res < 1) {
            throw BusinessException.errorOfCode("系统异常", "500");
        }
        //再写缓存
        String cacheKey = "u:" + userId + ":sign:" + formatDate;
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        valueOperations.setBit(cacheKey, offset, true);
    }

    /**
     * 检查当前这个人userId在这个天date有没有签到过
     *
     * @param userId
     * @param localDate
     * @return
     */
    @Override
    public boolean checkSign(Long userId, Date localDate) {
        //检查缓存
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMM");
        String formatDate = simpleDateFormat.format(localDate);
        String cacheKey = "u:" + userId + ":sign:" + formatDate;
        int offset = DateUtil.dayOfMonth(localDate) - 1;
        ValueOperations<String, Object> valueOperations = this.redisTemplate.opsForValue();
        Boolean res = valueOperations.getBit(cacheKey, offset);

        return res != null ? res : false;
    }

    /**
     * 获取用户指定时间所在月份（从当月1号到改天）的签到次数
     *
     * @param userId
     * @param date
     * @return
     */
    @Override
    public long getUserSignCount(Long userId, Date date) {
        ValueOperations<String, Object> valueOperations = this.redisTemplate.opsForValue();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMM");
        String formatDate = simpleDateFormat.format(date);
        String cacheKey = "u:" + userId + ":sign:" + formatDate;
        int offset = DateUtil.dayOfMonth(date);
        return valueOperations.getOperations().execute(
                new RedisCallback<Long>() {
                    @Override
                    //返回的是從當月1號到当天的所有签到的总次数
                    public Long doInRedis(RedisConnection connection) throws DataAccessException {
                        BitFieldSubCommands.BitFieldGet bitFieldGet = BitFieldSubCommands
                                .BitFieldGet.create(BitFieldSubCommands.BitFieldType.unsigned(offset), BitFieldSubCommands.Offset.offset(0));
                        BitFieldSubCommands subCommands = BitFieldSubCommands.create(bitFieldGet);
                        List<Long> resList = connection.bitField(cacheKey.getBytes(), subCommands);
                        //把long数字转换成字节数组，统计里面存在多少个为1的次数
                        Long valueCount = resList.get(0);
                        //33 "0001 0001"
                        String binString = Long.toBinaryString(valueCount);
                        return Stream.of(binString.toCharArray()).filter(item -> item.equals('1')).count();
                    }
                }
        );
    }

    @Override
    public String getFirstSignMonthDay(Long userId, Date date) {
        ValueOperations<String, Object> valueOperations = this.redisTemplate.opsForValue();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMM");
        String formatDate = simpleDateFormat.format(date);
        String cachedKey = "u:" + userId + ":sign:" + formatDate;
        Long val = valueOperations.getOperations().execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.bitPos(cachedKey.getBytes(), true, Range.closed(0L, 31L));
            }
        });
        if (val != null && val > -1) {
            return DateUtil.offsetDay(date, val.intValue()).toDateStr();
        }
        return null;
    }
}


